关系
任何两个RealmObject都可以链接在一起。
1 | public class Email extends RealmObject { |
关系在Realm中开销不大。这意味着保持连接并不会影响速度,并且关系的内部实现在使用内存上也是相当高效的。
多对一
在类中简单的定义一个继承自RealmObject的子类作为成员即可:
1 | public class Contact extends RealmObject { |
每个联系人(Contact的实例)都有0或1个电子邮件(Email实例)。在Realm中,没有什么可以阻止你在多个联系人中使用相同的电子邮件对象,上述模型可以是多对一关系,但通常用于建模一对一关系。
设置RealmObject字段null将清除引用,但对象不会从Realm中删除
多对多
你可以通过RealmList<T>字段声明从单个对象与任意数量的对象建立关系。例如,假设有多个电子邮件地址的联系人
1 | public class Contact extends RealmObject { |
RealmLists基本上是RealmObjects的容器,并且RealmList和一个普通的Java List用法基本相同。Realm在不同的RealmLists中可以使用相同的对象两次(或更多),没有限制,因此你可以使用它来建模一对多和多对多关系。
你可以创建对象,并使用RealmList.add()将Email对象添加到Contact对象:
1 | realm.executeTransaction(new Realm.Transaction() { |
可以声明递归关系,当建模某些类型的数据时,这些关系可能很有用
1 | public class Person extends RealmObject { |
将RealmList字段的值设置null将清除列表。也就是说,列表将为空(长度为零),但没有对象会被删除。通过getter方法获取RealmList将永远不会返回null。返回的对象总是一个列表,但长度可能为零。
连接查询
查询连接和关系时可以的。考虑如下模型:
1 | public class Person extends RealmObject { |
每个Person可以和狗建立一对多的关系,正如下表所示:
使用连接查询来找一些人
1 | // persons => [U1,U2] |
首先,请注意,条件中的字段名称”equalTo”包含通过关系的路径(以句点分隔.)。
上述查询应读取,查找到拥有名为”布朗”的狗的所有人。重要的是要理解,结果将包含不满足条件的Dog对象,因为它们是Person对象的一部分。
1 | persons.get(0).getDogs(); // => [A,B] |
这可以通过以下两个查询进一步检查。
1 | // r1 => [U1,U2] |
注意第一个查询如何返回这两个Person对象,因为条件与两个人都匹配。每个Person查询结果都包含一个Dog对象列表- 它们的所有Dog对象(即使是那些不满足原始查询条件的对象)。记住,我们正在寻找有特定种类的Dog(姓名和颜色)的人,而不是实际的狗本身。因此,第二个查询将针对第一个Person查询result(r1)和每个Persons狗进行评估。第二个查询也匹配两个人,但这次是因为狗的颜色。
让我们深入一点,以帮助巩固这个概念。请查看以下示例:
1 | / r1 => [U1,U2] |
第一个查询应该读取,查找到所有有名为”Fluffy”狗的 Person,也找到那些有名为”Brown”狗的Person,然后给我两个的交集。第二个查询先读取并查找到所有名为“Fluffy”的狗的Person。然后,给定结果集,找到所有有颜色为“棕色”的狗的Person,并且给定结果集,找到具有颜色为“黄色”的狗的所有人。
让我们来看看背后的查询,r1以便充分了解发生了什么。两个条件是equalTo(“dogs.name”, “Fluffy”)和equalTo(“dogs.color”, “Brown”)。第一个条件满足U1和U2- 这是C1集合。第二个条件也满足U1和U2- 这是C2集合。查询中的逻辑和,和C1和C2的交集是一样的。C1和C2的交集为U1 and U2。因此,r1是U1 and U2。
后面的查询r2就不同了。让我们开始分解这次查询以探究竟。查询的第一部分如下所示:RealmResults<Person> r2a = realm.where(Person.class).equalTo(“dogs.name”, “Fluffy”).findAll();。它匹配U1和U2。然后,r2b = r2a.where().equalTo(“dogs.color”, “Brown”).findAll();还是匹配U1和U2(两个人都有棕色的狗)。最后的查询,r2 = r2b.where().equalTo(“dogs.color”, “Yellow”).findAll();只匹配U2,因为只有一个人在棕色狗结果集中有一个黄色的狗,也就是U2
写
读操作是隐式的,这意味着可以随时访问和查询对象。所有写操作(添加,修改和删除对象)必须包含在写事务中。写事务可以提交或取消。在提交期间,所有更改都将写入磁盘,并且提交只有在所有更改都可以保留时才会成功。通过取消写入事务,所有更改都将被丢弃。使用写事务,你的数据将始终处于一致状态。
写事务也用于确保线程安全
1 | // Obtain a Realm instance |
在写事务中使用到你的RealmObjects时,你可能最后想放弃修改。与其提交之后再回退,你可以简单地取消写事务。
1 | realm.beginTransaction(); |
请注意,写事务可能会相互阻塞。如果你在UI线程和后台线程上同时创建写入事务,这可能会导致ANR错误。为了避免这种情况,在UI线程上创建写事务时使用异步事务。
Realm是崩溃安全的,所以在一次事务中发生了Exception,Realm本身不会崩溃。只是当前事务中的数据会丢失。如果异常被捕获但应用程序仍在继续,这个时候取消正在执行的事务就显得尤为重要。如果使用executeTransaction(),这些将会自动的执行。
感谢Realm的MVCC架构,在写事务打开的时候读取不会被阻塞!这意味着,除非你需要从多个线程同时进行事务,你可以使用一个更大的事务,这些事务如果使用小事务来做的话将会耗费更多的工作量。当你将写事务提交到Realm时,该Realm的所有其他实例将被通知并自动更新。
Realm中的读写访问是ACID
创建对象
因为RealmObjects都得绑定到一个领域,他们应该通过Realm直接实例化
1 | realm.beginTransaction(); |
或者,你可以首先创建一个对象的实例,然后使用realm.copyToRealm()添加它。Realm支持尽可能多的自定义构造函数,只要其中一个是公共的无参数构造函数。
1 | User user = new User("John"); |
当使用时,realm.copyToRealm()重要的是要记住只有返回的对象由Realm管理,所以对原始对象的任何进一步的更改都不会被持久化
事务块
不用手动保持跟踪realm.beginTransaction(),realm.commitTransaction()以及realm.cancelTransaction()可以使用realm.executeTransaction()方法,它会自动处理开始/提交,发生错误的时候将自动取消
1 | realm.executeTransaction(new Realm.Transaction() { |
异步事务
因为事务被其他事务阻塞,所以在后台线程上执行所有写操作以避免阻塞UI线程是一个很好的主意。通过使用异步事务,Realm将在后台线程上运行该事务并在事务完成时报告。
1 | realm.executeTransactionAsync(new Realm.Transaction() { |
OnSuccess和OnError回调都是可选的,但是如果提供,它们将在事务成功完成或失败时分别被调用。回调由Looper控制,因此它们只允许在Looper线程上回调。
1 | ealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() { |
异步事务由RealmAsyncTask对象表示。如果在事务完成之前退出Activity / Fragment,此对象可用于取消任何挂起的事务。如果回调更新UI,忘记取消事务可能会导致应用程序崩溃。
1 | ublic void onStop () { |
更新字符串和字节数
Realm是以完整的字段作为操作对象的,不可能更新字符串或字节数组的单个元素。假如你需要更新字符串的第5个元素,你将需要做类似的事情:
1 | realm.executeTransaction(new Realm.Transaction() { |
是由于Realm的MVCC架构,它避免了对现有数据的改变,以确保读取数据的其他线程或进程下表现一致。
快照
所有Realm集合都是实时的。这意味着它们总是反映最新的状态。在大多数情况下,这是可取的,但如果你循环一个集合,目的是修改元素呢?例如:
1 | RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll(); |
通常你会期望这个简单的循环邀请所有客人。因为RealmResults是立即更新,虽然,只有一半的客人最终被邀请!受邀的邀请对象将从该集合中立即删除,这会移动所有元素。当i参数增加时,它将错过一个元素。
为了防止这种情况,你可以拍摄集合数据的快照。快照保证元素的顺序不会改变,即使元素被删除或修改。
Iterator从Realm集合创建的文件将自动使用快照。RealmResults和RealmList可以使用createSnapshot()手动创建快照。
1 | RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll(); |